﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Xml;

namespace weatherforecaster
{
    // Class to communicate with the waether API service.
    class ClsWunderground
    {
        // Defines a structure for City and State with minimal parsing for the weather API.
        private struct CityState
        {
            private const string Sprtr = "_";
                        
            private string pFullText;
            public string FullText
            {
               get { return pFullText; }
               set
               {
                   if (value != null)
                   {
                       pFullText = value.Trim();
                       if (value.IndexOf(",") != -1)         // checks if a "," is found
                       { // "," was found
                           pCity = this.ToTitleCase(pFullText.Remove(pFullText.IndexOf(",")).Trim());
                           pState = pFullText.Substring(pFullText.IndexOf(",") + 1).Trim().ToUpper();
                           pHasState = true;
                           pFullText = pCity + ", " + pState;
                       }
                       else
                       { // "," was NOT found -> there is no state in the string
                           pCity = this.ToTitleCase(pFullText);
                           pState = null;
                           pHasState = false;
                       }
                   }
               }
            }

            // Returns the City only, with the Seperator in between words
            private string pCity;
            public string City
            {
                get { return pCity; }      
            }

            // Returns a boolean of the existance of a state in the search string
            private bool pHasState;
            public bool HasState
            {
                get { return pHasState; }
            }

            // Returns the State only
            private string pState;
            public string State
            {
                get { return pState; }
            }

            // Converts string to Title Case and replaces spaces with seperator
            private string ToTitleCase(string SrcString)
            {
                string Rslt = "";
                Rslt = SrcString[0].ToString().ToUpper();
                for (int i = 1; i < SrcString.Length; i++)
                {
                    if (SrcString[i].ToString() == " ")
                    {
                        Rslt = Rslt + Sprtr;
                    }
                    else
                    {
                        if (SrcString[i - 1].ToString() == " ")
                        {
                            Rslt = Rslt + SrcString[i].ToString().ToUpper();
                        }
                        else
                        {
                            Rslt = Rslt + SrcString[i].ToString().ToLower();
                        }
                    }
                }

                return Rslt;
            }

        }

        public struct ListOfCityStateCountry
        {
            public string City;
            public string State;
            public string Country;
            public void ClearData()
            {
                City = null;
                State = null;
                Country = null;
            }

       }

        public struct Forecast
        {
            public DateTime Date;
            public int YYYY;
            public byte MM;
            public string Month;
            public byte DD;
            public string Day;
            public byte Period;
            public short HighF;
            public short HighC;
            public short LowF;
            public short LowC;
            public string Condition;
            public string ConditionIcon;
            public string Location;
            public void ClearData()
            {
                Date = DateTime.MinValue;
                YYYY = 0;
                MM = 0;
                Month = null;
                DD = 0;
                Period = 0;
                HighF = 1000;
                HighC = 1000;
                LowF = 1000;
                LowC = 1000;
                Condition = null;
                ConditionIcon = null;
                Location = null;
            }
        }
        
        public static List<ListOfCityStateCountry> RsltsCtySttCty = new List<ListOfCityStateCountry>();
        public static List<Forecast> RsltsFrcst = new List<Forecast>();

        // Connects to the weather service and retreives the weather forecast for a given city and an optional state
        // If the city is ambiguous, it retrieve the known options.
        private static Stream QueryWeather(string Location)
        {
            #region BuildQuery
            CityState Loc = new CityState();
            Loc.FullText = Location;

            // Creates the query URL string
            string QryURL = weatherforecaster.Properties.Settings.Default.WundergroundURL +
                            weatherforecaster.Properties.Settings.Default.WundergroundKey +
                            weatherforecaster.Properties.Settings.Default.WundergroundQry;
            if (Loc.HasState == false)
            {
                QryURL += Loc.City + weatherforecaster.Properties.Settings.Default.WundergroundSfx;
            }
            else
            {
                QryURL += Loc.State + "/" + Loc.City + weatherforecaster.Properties.Settings.Default.WundergroundSfx;
            }
            #endregion

            #region GetData
            WebRequest WR;
            WR = WebRequest.Create(QryURL);
            WR.Proxy = WebRequest.GetSystemWebProxy();

            #endregion
            return WR.GetResponse().GetResponseStream();
        }

        // Gets the data stream and parses it.
        public static string ParseResult(string Location)
        {
            string ResponseType = "";                // "Ambiguous", "Forecast" or "Not Found"
            RsltsCtySttCty.Clear();
            RsltsFrcst.Clear();

            // Convert the Stream to a StreamReader
            StreamReader Strm = new StreamReader(QueryWeather(Location));
            // Create a Xml reader from the stream
            XmlTextReader Rdr = new XmlTextReader(Strm);

            // Start parsing the XML
            while (Rdr.Read())
            {
                // Looking for results ("results") of ambiguous search or for a returned forecast ("forecast")
                if (Rdr.NodeType == XmlNodeType.Element)
                {
                    switch (Rdr.Name)
                    {
                        case "results":         // Response for ambiguous search. Need to collect all options.
                            ResponseType = "Ambiguous";
                            goto Ambiguous;
                        case "simpleforecast":  // Forecast was provided. Need to collect all data.
                            ResponseType = "Forecast";
                            goto Forecast;
                        case "error":           // No match found.
                            ResponseType = "Not Found";
                            goto EndOfParsing;
                        default:
                            break;
                    }
                }
            }

        #region Ambiguous
        Ambiguous:
            ListOfCityStateCountry tmpCtySttCty = new ListOfCityStateCountry();
            tmpCtySttCty.ClearData();

            while (Rdr.Read())
            {
                switch (Rdr.NodeType)
                {
                    case XmlNodeType.Element:
                        switch (Rdr.Name)
                        {
                            case "city":
                                Rdr.Read();
                                tmpCtySttCty.City = Rdr.Value;
                                break;
                            case "state":
                                Rdr.Read();
                                tmpCtySttCty.State = Rdr.Value;
                                break;
                            case "country_name":
                                Rdr.Read();
                                tmpCtySttCty.Country = Rdr.Value;
                                break;
                            default:
                                break;
                        }
                        break;
                    case XmlNodeType.EndElement:
                        switch (Rdr.Name)
                        {
                            case "result":      // End of specific result
                                RsltsCtySttCty.Add(tmpCtySttCty);
                                tmpCtySttCty.ClearData();
                                break;
                            case "results":     // No more results
                                goto EndOfParsing;
                            default:
                                break;
                        }
                        break;
                    default:
                        break;
                }
            }
            goto EndOfParsing;
            #endregion

        #region Forecast
        Forecast:
            Forecast tmpFrcst = new Forecast();
            tmpFrcst.ClearData();

            while (Rdr.Read())
            {
                switch (Rdr.NodeType)
                {
                    case XmlNodeType.Element:
                        switch (Rdr.Name)
                        {
                            case "day":
                                Rdr.Read();
                                tmpFrcst.DD = Convert.ToByte(Rdr.Value);
                                break;
                            case "weekday_short":
                                Rdr.Read();
                                tmpFrcst.Day = Rdr.Value;
                                break;
                            case "month":
                                Rdr.Read();
                                tmpFrcst.MM = Convert.ToByte(Rdr.Value);
                                break;
                            case "monthname":
                                Rdr.Read();
                                tmpFrcst.Month = Rdr.Value.Remove(3);
                                break;
                            case "year":
                                Rdr.Read();
                                tmpFrcst.YYYY = Convert.ToInt16(Rdr.Value);
                                break;
                            case "period":
                                Rdr.Read();
                                tmpFrcst.Period = Convert.ToByte(Rdr.Value);
                                break;
                            case "high":
                                Rdr.Read();
                                Rdr.Read();
                                Rdr.Read();
                                tmpFrcst.HighF = Convert.ToInt16(Rdr.Value);
                                Rdr.Read();
                                Rdr.Read();
                                Rdr.Read();
                                Rdr.Read();
                                tmpFrcst.HighC = Convert.ToInt16(Rdr.Value);
                                break;
                            case "low":
                                Rdr.Read();
                                Rdr.Read();
                                Rdr.Read();
                                tmpFrcst.LowF = Convert.ToInt16(Rdr.Value);
                                Rdr.Read();
                                Rdr.Read();
                                Rdr.Read();
                                Rdr.Read();
                                tmpFrcst.LowC = Convert.ToInt16(Rdr.Value);
                                break;
                            case "conditions":
                                Rdr.Read();
                                tmpFrcst.Condition = Rdr.Value;
                                break;
                            case "icon_url":
                                Rdr.Read();
                                tmpFrcst.ConditionIcon = Rdr.Value;
                                break;
                            default:
                                break;
                        }
                        break;
                    case XmlNodeType.EndElement:
                        switch (Rdr.Name)
                        {
                            case "forecastday":      // End of specific forecast period
                                tmpFrcst.Date = new DateTime(tmpFrcst.YYYY, tmpFrcst.MM, tmpFrcst.DD);
                                RsltsFrcst.Add(tmpFrcst);
                                tmpFrcst.ClearData();
                                break;
                            case "forecastdays":     // No more forecast periods
                                goto EndOfParsing;
                            default:
                                break;
                        }
                        break;
                    default:
                        break;
                }
            }
            goto EndOfParsing;
            #endregion

        EndOfParsing:
            return ResponseType;
        }

    }
}
